package org.eclipse.buildship.ui.view.execution

import org.gradle.tooling.events.OperationDescriptor
import org.gradle.tooling.events.StartEvent
import org.gradle.tooling.events.task.TaskOperationDescriptor
import org.gradle.tooling.events.test.JvmTestOperationDescriptor
import org.hamcrest.core.IsAnything

import com.gradleware.tooling.toolingclient.GradleDistribution

import org.eclipse.core.resources.IProject
import org.eclipse.core.runtime.NullProgressMonitor
import org.eclipse.debug.core.ILaunchConfiguration
import org.eclipse.swtbot.eclipse.finder.waits.Conditions
import org.eclipse.ui.IEditorReference

import org.eclipse.buildship.core.CorePlugin
import org.eclipse.buildship.core.Logger
import org.eclipse.buildship.core.configuration.RunConfiguration
import org.eclipse.buildship.core.launch.GradleRunConfigurationAttributes
import org.eclipse.buildship.core.util.gradle.GradleDistributionSerializer
import org.eclipse.buildship.ui.test.fixtures.SwtBotSpecification

class OpenTestSourceFileJobTest extends SwtBotSpecification {

    void cleanup() {
        bot.closeAllEditors()
    }

    def "Opening sources fail silently if the source file is not available"() {
        setup:
        Logger logger = Mock(Logger)
        environment.registerService(Logger, logger)
        File projectDir = dir('sample-project')
        importAndWait(projectDir)

        when:
        openSourceAndWait(projectDir, classOperationItem(':test', 'NonexistingClassName'))
        openSourceAndWait(projectDir, methodOperationItem(':test', 'NonexistingClassName', 'NonexistingMethodName'))

        then:
        bot.editors().isEmpty()
        0 * logger.warn(*_)
        0 * logger.error(*_)
    }

    def "Opening sources fail silently if containing project is closed" () {
        setup:
        Logger logger = Mock(Logger)
        environment.registerService(Logger, logger)

        File projectDir = createSingleModuleProject()
        importAndWait(projectDir)
        IProject project = findProject('sample-project')
        project.close(new NullProgressMonitor())

        when:
        openSourceAndWait(projectDir, classOperationItem(':test', 'MyJavaTest'))

        then:
        bot.editors().isEmpty()
        0 * logger.warn(*_)
        0 * logger.error(*_)
    }

    def "Can open sources in a single-module build"() {
        setup:
        File projectDir = createSingleModuleProject()
        importAndWait(projectDir)

        when:
        openSourceAndWait(projectDir, classOperationItem(':test', 'MyJavaTest'))

        then:
        oneEditorIsOpened()
        activeEditorInputPath() == '/sample-project/src/test/java/MyJavaTest.java'

        when:
        openSourceAndWait(projectDir, methodOperationItem(':test', 'MyJavaTest', 'javaTest'))

        then:
        oneEditorIsOpened()
        activeEditorInputPath() == '/sample-project/src/test/java/MyJavaTest.java'

        when:
        openSourceAndWait(projectDir, classOperationItem(':test', 'MyGroovyTest'))

        then:
        oneEditorIsOpened()
        activeEditorInputPath() == '/sample-project/src/test/groovy/MyGroovyTest.groovy'

        when:
        openSourceAndWait(projectDir, methodOperationItem(':test', 'MyGroovyTest', 'groovyTest'))

        then:
        oneEditorIsOpened()
        activeEditorInputPath() == '/sample-project/src/test/groovy/MyGroovyTest.groovy'
    }


    def "Can open sources in a multi-module build"(String projectPath, String expectedJavaFilePath, String expectedGroovyFilePath) {
        setup:
        File projectDir = createMultiModuleProject()
        importAndWait(projectDir)

        when:
        openSourceAndWait(projectDir, classOperationItem(projectPath, 'MyJavaTest'))

        then:
        oneEditorIsOpened()
        activeEditorInputPath() == expectedJavaFilePath


        when:
        openSourceAndWait(projectDir, classOperationItem(projectPath, 'MyGroovyTest'))

        then:
        oneEditorIsOpened()
        activeEditorInputPath() == expectedGroovyFilePath

        where:
        projectPath  | expectedJavaFilePath                  | expectedGroovyFilePath
        ':sub1:test' | '/sub1/src/test/java/MyJavaTest.java' | '/sub1/src/test/groovy/MyGroovyTest.groovy'
        ':sub2:test' | '/sub2/src/test/java/MyJavaTest.java' | '/sub2/src/test/groovy/MyGroovyTest.groovy'

    }

    def "If the workspace project can't be identified then the search falls back to the workspace scope"() {
        setup:
        File projectDir = createSingleModuleProject()
        importAndWait(projectDir)

        when:
        openSourceAndWait(projectDir, classOperationItem(':nonexistingmodule:test', 'MyJavaTest'))

        then:
        oneEditorIsOpened()
        activeEditorInputPath() == '/sample-project/src/test/java/MyJavaTest.java'

        when:
        openSourceAndWait(projectDir, classOperationItem(':nonexistingmodule:test', 'MyGroovyTest'))

        then:
        oneEditorIsOpened()
        activeEditorInputPath() == '/sample-project/src/test/groovy/MyGroovyTest.groovy'
    }

    private File createSingleModuleProject() {
        File projectDir = dir('sample-project') {
            file 'settings.gradle'
            file 'build.gradle',
            """allprojects {
                   apply plugin: 'groovy'
                   repositories { jcenter() }
                   dependencies { testCompile 'org.codehaus.groovy:groovy:2.4.6' }
              }
            """
            dir('src/test/java') { file 'MyJavaTest.java', 'public class MyJavaTest { void javaTest() {} }' }
            dir('src/test/groovy') { file 'MyGroovyTest.groovy', 'class MyGroovyTest { void groovyTest() {} }' }
        }
    }

    private File createMultiModuleProject() {
        File projectDir = dir('sample-multimodule-project') {
            file 'settings.gradle', 'include "sub1"; include "sub2"'
            file 'build.gradle',
            """allprojects {
                   apply plugin: 'groovy'
                   repositories { jcenter() }
                   dependencies { testCompile 'org.codehaus.groovy:groovy:2.4.6' }
              }
            """
            dir('sub1/src/test/java') { file 'MyJavaTest.java', 'public class MyJavaTest { void javaTest() {} }' }
            dir('sub2/src/test/java') { file 'MyJavaTest.java', 'public class MyJavaTest { void javaTest() {} }' }
            dir('sub1/src/test/groovy') { file 'MyGroovyTest.groovy', 'class MyGroovyTest { void groovyTest() {} }' }
            dir('sub2/src/test/groovy') { file 'MyGroovyTest.groovy', 'class MyGroovyTest { void groovyTest() {} }' }
        }
    }

    private void openSourceAndWait(projectDir, OperationItem operationItem, boolean existingSource = true) {
        bot.closeAllEditors()
        RunConfiguration runConfiguration = CorePlugin.configurationManager().loadRunConfiguration(launchConfigurationFor(projectDir))
        OpenTestSourceFileJob job = new OpenTestSourceFileJob([operationItem], runConfiguration)
        job.schedule()
        job.join()
    }

    private OperationItem methodOperationItem(String taskPath, String className, String methodName) {
        StartEvent startEvent = Mock(StartEvent)
        startEvent.getDescriptor() >> testMethodDescriptor(taskPath, className, methodName)
        new OperationItem(startEvent)
    }

    private OperationItem classOperationItem(String taskPath, String className) {
        StartEvent startEvent = Mock(StartEvent)
        startEvent.getDescriptor() >> testClassDescriptor(taskPath, className)
        new OperationItem(startEvent)
    }

    private JvmTestOperationDescriptor testMethodDescriptor(String taskPath, String className, String methodName) {
        JvmTestOperationDescriptor descriptor = operationDescriptorMock(methodName, "Test $methodName($className)", JvmTestOperationDescriptor)
        descriptor.getClassName() >> className
        descriptor.getMethodName() >> methodName
        descriptor.getParent() >> testClassDescriptor(taskPath, className)
        descriptor
    }

    private JvmTestOperationDescriptor testClassDescriptor(String taskPath, String className) {
        JvmTestOperationDescriptor descriptor = operationDescriptorMock(className, "Test class $className", JvmTestOperationDescriptor)
        descriptor.getClassName() >> className
        descriptor.getParent() >> testTaskDescriptor(taskPath)
        descriptor
    }

    private OperationDescriptor testTaskDescriptor(String taskPath) {
        OperationDescriptor executer = operationDescriptorMock('Gradle Test Executor 1')
        OperationDescriptor testRun = operationDescriptorMock("Gradle Test Run $taskPath")
        TaskOperationDescriptor testTask = operationDescriptorMock(taskPath, TaskOperationDescriptor)
        testTask.getTaskPath() >> taskPath
        executer.getParent() >> testRun
        testRun.getParent() >> testTask
        executer
    }

    private OperationDescriptor operationDescriptorMock(String name, Class cls = OperationDescriptor) {
        operationDescriptorMock(name, name, cls)
    }

    private OperationDescriptor operationDescriptorMock(String name, String displayName, Class cls = OperationDescriptor) {
        def descriptor = Mock(cls)
        descriptor.getName() >> name
        descriptor.getDisplayName() >> displayName
        descriptor
    }

    private ILaunchConfiguration launchConfigurationFor(File projectDir) {
        GradleRunConfigurationAttributes attributes = new GradleRunConfigurationAttributes(
            [],
            projectDir.absolutePath,
            GradleDistributionSerializer.INSTANCE.serializeToString(GradleDistribution.fromBuild()),
            null,
            null,
            [],
            [],
            true,
            true,
            false,
            false,
            false)
        CorePlugin.gradleLaunchConfigurationManager().getOrCreateRunConfiguration(attributes)
    }

    private boolean oneEditorIsOpened() {
        bot.waitUntil(Conditions.waitForEditor(new IsAnything<IEditorReference>()))
        bot.editors().size() == 1
    }

    private String activeEditorInputPath() {
        bot.waitUntil(Conditions.waitForEditor(new IsAnything<IEditorReference>()))
        bot.activeEditor().reference.editorInput.file.fullPath
    }
}
